#include "level_non_zero.h"
#include "entry.h"
#include "common.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
#include <unistd.h>

namespace VirtualR {

LevelNonZero::LevelNonZero(std::string dir) : dir_(dir) {
  if (!access(dir_.c_str(), 0)) {
    // 文件夹存在
    std::string tmp_file = dir_ + "/index";
    uint32_t key_size = 0;
    FILE *file = fopen(tmp_file.c_str(), "r");
    if (file == NULL) {
      fclose(file);
      dump_index_to_disk();
    } else {
      fread(&file_count_, sizeof(uint64_t), 1, file);
      fread(&total_byte_size_, sizeof(uint64_t), 1, file);
      fread(&key_size, sizeof(uint32_t), 1, file);
    
      char str[key_size + 1];
      fread(str, key_size, 1, file);
      str[key_size] = '\0';
      std::string value(str);
      last_key_ = value;

      for (uint64_t i = 0; i < file_count_; i++) {
        uint64_t no = 0;
        fread(&no, sizeof(uint64_t), 1, file);
        SSTableID id(dir_, no);
        SSTable sst(id);
        sst_list_.push_back(sst);
      }
    }
    fclose(file);
  } else {
    // 文件夹不存在
    // have some bug
    int flag = mkdir(dir_.c_str(), S_IRWXU);
    if (flag != 0) {
        std::cout << "create dir error: " << dir_ << std::endl;
        assert(false);
    }
    dump_index_to_disk();
  }
}

void LevelNonZero::dump_index_to_disk() {
  std::string tmp_file = dir_ + "/index";
  FILE *file = fopen(tmp_file.c_str(), "w+");
  if (file == nullptr) {
    std::cout << "dump index to disk file faild: " << tmp_file << std::endl;
    assert(false);
  }
  auto size_d = 0;
  size_d = fwrite(&file_count_, sizeof(uint64_t), 1, file);
  if (size_d != 1) {
    std::cout << "LevelNonZero::dump_index_to_disk file_count_ error" << std::endl;
    assert(false);
  }
  
  size_d = fwrite(&total_byte_size_, sizeof(uint64_t), 1, file);
  if (size_d != 1) {
    std::cout << "LevelNonZero::dump_index_to_disk file_count_ error" << std::endl;
    assert(false);
  }

  uint32_t key_size = last_key_.size();
  size_d = fwrite(&key_size, sizeof(uint32_t), 1, file);
  if (size_d != 1) {
    std::cout << "LevelNonZero::dump_index_to_disk file_count_ error" << std::endl;
    assert(false);
  }

  if (!last_key_.empty()) {
    char arr[last_key_.size() + 1];
    strcpy(arr, last_key_.c_str());

    size_d = fwrite(arr, sizeof(char), last_key_.size(), file);
    if (size_d != 1) {
        std::cout << "LevelNonZero::dump_index_to_disk file_count_ error" << std::endl;
        assert(false);
    }
  }

  for (auto tmp : sst_list_) {
    uint64_t tmp_no = tmp.no();
    size_d = fwrite(&tmp_no, sizeof(uint64_t), 1, file);
    if (size_d != 1) {
      std::cout << "LevelNonZero::dump_index_to_disk file_count_ error" << std::endl;
      assert(false);
    }
  }
  fclose(file);
}

void LevelNonZero::merge(std::vector<Entry_t> entries, uint64_t &no) {
  if (entries.empty()) {
    std::cout << "entries is empty" << std::endl;
    return;
  }

  std::string low = entries[0].key;
  std::string high = entries.back().key;
  std::vector<Entry_t> res, tmp;
  auto it = sst_list_.begin();
  while (it != sst_list_.end() && it->higher() < low) {
    it++;
  }
  auto tmp_it = it;
  while (it != sst_list_.end() && it->lower() <= high && file_count_ != 0) {
    std::vector<Entry_t> entries = it->load();
    total_byte_size_ -= it->disk_size();
    file_count_ -= 1;
    it->remove_file();
    tmp_it++;
    sst_list_.erase(it);
    it = tmp_it;
  }

  res = do_merge(entries, tmp);

  uint64_t pos = 0;
  while (pos < res.size()) {
    SSTableID id(dir_, no++);
    SSTable table(res, pos, id);
    sst_list_.push_back(table);
    file_count_++;
    total_byte_size_ += table.disk_size();
  }
  dump_index_to_disk();
}

void LevelNonZero::clear() {
  while (!sst_list_.empty()) {
    sst_list_.back().remove_file();
    sst_list_.pop_back();
  }

  file_count_ = 0;
  total_byte_size_ = 0;
  dump_index_to_disk();
}

uint64_t LevelNonZero::size() {
  return total_byte_size_;
}

std::pair<bool, std::string> LevelNonZero::search(std::string key) {
  std::pair<bool, std::string> res;

  auto it = sst_list_.begin();
  while (it != sst_list_.end()) {
    res = it->search(key);
    if (res.first == true) {
        return res;
    }
    it++;
  }
  return res;
}

std::vector<Entry_t> LevelNonZero::get_entry() {
  auto it = sst_list_.begin();
  while (it != sst_list_.end() && it->higher() <= last_key_) {
    it++;
  }
  if (it == sst_list_.end()) {
    it = sst_list_.begin();
  }
  total_byte_size_ -= it->disk_size();
  last_key_ = it->higher();
  std::vector<Entry_t> res = it->load();
  it->remove_file();
  sst_list_.erase(it);
  file_count_--;
  dump_index_to_disk();
  return res;
}

void LevelNonZero::get_index_content_for_debug() {
  std::string tmp_file = dir_ + "/index";
  FILE *file = fopen(tmp_file.c_str(), "r");
  uint64_t d_file_count = 0, d_total_byte_size = 0, d_tmp_sst_no = 0;
  uint32_t d_key_size = 0;
  std::vector<uint64_t> d_sst_nos;
  fread(&d_file_count, sizeof(uint64_t), 1, file);
  fread(&d_total_byte_size, sizeof(uint64_t), 1, file);
  fread(&d_key_size, sizeof(uint32_t), 1, file);
  char str[d_key_size + 1];
  fread(str, d_key_size, 1, file);
  str[d_key_size] = '\0';
  std::string value(str);

  for (uint32_t i = 0; i < d_file_count; i++) {
    fread(&d_tmp_sst_no, sizeof(uint64_t), 1, file);
    d_sst_nos.push_back(d_tmp_sst_no);
  }
  std::cout << "file_count: " << d_file_count << " total_byte: " << d_total_byte_size << std::endl;
  std::cout << "sst nos:";
  for (auto x : d_sst_nos) {
    std::cout << " " << x;
  }
  fclose(file);
}

}